#include "tool/selecttool.h"

#include "view/scene.h"
#include "view/view.h"
#include "item/item.h"

#include "xdl/symbol.h"

#include "propertyeditor/itempropertyeditor.h"

#include "objectinspector/objectinspectorview.h"
#include "objectinspector/objectinspectormodel.h"

#include "command/setpropertycommand.h"
#include "command/aligncommand.h"
#include "command/distributecommand.h"
#include "command/stackordercommand.h"

#include <QMouseEvent>
#include <QKeyEvent>
#include <QAction>
#include <QHeaderView>
#include <QGridLayout>
#include <QToolBar>
#include <QLabel>
#include <QDebug>

namespace SymbolEditor
{

    SelectTool::SelectTool(QObject *parent):
        AbstractTool(parent)
    {

        setLabel("Select <i>Esc</i>");
        setShortcut(QKeySequence(Qt::Key_Escape));
        setIcon(QIcon::fromTheme("edit-select"));
        updateAction();

        setupArrangeTools();
        setupObjectInspector();
        setupPropertyBrowser();
        setWidgets(QList<QWidget*>() << m_arrangeWidget << m_objectInspectorView << m_propertyEditor);
    }

    SelectTool::~SelectTool()
    {

    }

    void SelectTool::addDocumentItem(quint64 id, const Item *item)
    {
        m_objectInspectorModel->addTopLevelItem(id, item->friendlyTypeName(), item->icon());
        m_objectInspectorModel->setItemVisibility(id, item->isVisible());
        m_objectInspectorModel->setItemLockState(id, item->isLocked());
    }

    void SelectTool::updateDocumentItem(quint64 id, const Item *item)
    {
        m_objectInspectorModel->setItemVisibility(id, item->isVisible());
        m_objectInspectorModel->setItemLockState(id, item->isLocked());
    }

    void SelectTool::updateDocumentItemProperty(quint64 itemId, quint64 propertyId, const QVariant &value)
    {
        switch (propertyId)
        {
            case VisibilityProperty:
                // FIXME: Force selected to false if item is being hidden?
                m_objectInspectorModel->setItemVisibility(itemId, value.toBool());
                break;
            case LockedProperty:
                // FIXME: Force selected to false if item is being locked?
                m_objectInspectorModel->setItemLockState(itemId, value.toBool());
                break;
            default:
                break;
        }


        if (m_propertyEditor->item() != nullptr && m_propertyEditor->item()->id() == itemId)
        {
            m_propertyEditor->updateProperty(propertyId, value);
        }
    }

    void SelectTool::removeDocumentItem(quint64 id)
    {
        m_objectInspectorModel->removeItem(id);
    }

    void SelectTool::onObjectInspectorVisibilityChangeRequested(quint64 id, bool visibility)
    {
        auto command = new SetPropertyCommand();
        command->setItemId(id);
        command->setPropertId(VisibilityProperty);
        command->setPropertyValue(visibility);
        appendCommand(command);
    }

    void SelectTool::onObjectInspectorLockStateChangeRequested(quint64 id, bool lockState)
    {
        auto command = new SetPropertyCommand();
        command->setItemId(id);
        command->setPropertId(LockedProperty);
        command->setPropertyValue(lockState);
        appendCommand(command);
    }

    void SelectTool::onObjectInspectorSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
    {
#if 0
        QList<const Item*> selectedDocumentItems;
        for (const QModelIndex &index: m_objectInspectorView->selectionModel()->selectedIndexes())
        {
            auto id = m_objectInspectorModel->documentIdForIndex(index);
            // FIXME: We should give the property editor GraphicsItem pointers
            // Or documentItemId. Reason: Tools shoudn't depend on a Document instance
            auto item = document()->item(id);
            selectedDocumentItems.append(item);
        }
        m_propertyEditor->setItems(selectedDocumentItems);

        if (m_changingSelection)
        {
            return;
        }

        m_changingSelection = true;

        for (const QModelIndex &index: deselected.indexes())
        {
            auto id = m_objectInspectorModel->documentIdForIndex(index);
            auto item = scene()->itemForDocumentId(id);
            item->setSelected(false);
        }

        for (const QModelIndex &index: selected.indexes())
        {
            auto id = m_objectInspectorModel->documentIdForIndex(index);
            auto item = scene()->itemForDocumentId(id);
            item->setSelected(true);
        }

        m_changingSelection = false;
#endif
    }

    // TBD: consider relying on the view for rubber band
    // View has rubberbandChanged signal with from/to scene points
    // Connect it to scene->setSelectionArea wich takes a painter path
    void SelectTool::onSceneSelectionChanged()
    {
        // FIXME: manage enabling of arrange, distribute and stack action separately
        //bool enabled = scene()->selectedObjects().count() >= 2;
        //m_arrangeWidget->setEnabled(enabled);
#if 0
        if (m_changingSelection)
        {
            return;
        }

        m_changingSelection = true;

        auto selectionModel = m_objectInspectorView->selectionModel();
        selectionModel->clear();

        for (auto item: scene()->selectedObjects())
        {
            auto documentId = item->data(0).value<quint64>();
            auto modelIndex = m_objectInspectorModel->indexForDocumentId(documentId);
            selectionModel->select(modelIndex, QItemSelectionModel::Select|QItemSelectionModel::Rows);
        }

        m_changingSelection = false;
#endif
    }

    void SelectTool::activate(Scene *scene)
    {
        setScene(scene);

        if (scene == nullptr)
        {
            return;
        }

        onSceneSelectionChanged();
        connect(scene, &Scene::selectionChanged,
                this, &SelectTool::onSceneSelectionChanged);
#if 0
        connect(m_objectInspectorView->selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &SelectTool::onObjectInspectorSelectionChanged);
        connect(m_objectInspectorModel, &ObjectInspectorModel::itemVisiblityChangeRequested,
                this, &SelectTool::onObjectInspectorVisibilityChangeRequested);
        connect(m_objectInspectorModel, &ObjectInspectorModel::itemLockStateChangeRequested,
                this, &SelectTool::onObjectInspectorLockStateChangeRequested);
        connect(m_propertyEditor, &ItemPropertyEditor::commandRequested,
                this, &SelectTool::commandRequested);
#endif
    }

    void SelectTool::desactivate()
    {
        m_propertyEditor->disconnect(this);
        m_objectInspectorModel->disconnect(this);
        m_objectInspectorView->selectionModel()->disconnect(this);
        scene()->disconnect(this);
        setScene(nullptr);
    }
#if 0
    void SelectTool::mousePressEvent(QMouseEvent *event)
    {
        if (event->button() != Qt::LeftButton)
        {
            return;
        }

        event->accept();
    }

    void SelectTool::mouseMoveEvent(QMouseEvent *event)
    {
        event->accept();
    }

    void SelectTool::mouseReleaseEvent(QMouseEvent *event)
    {
        if (event->button() != Qt::LeftButton)
        {
            return;
        }

        auto item = view()->objectUnderMouse();
        if (item == nullptr)
        {
            scene()->clearSelection();
            event->accept();
            return;
        }

        if (!event->modifiers().testFlag(Qt::ShiftModifier))
        {
            scene()->clearSelection();
        }
        item->setSelected(!item->isSelected());
    }

    void SelectTool::keyPressEvent(QKeyEvent *event)
    {
        Q_UNUSED(event);
    }

    void SelectTool::keyReleaseEvent(QKeyEvent *event)
    {
        Q_UNUSED(event);
    }
#endif

    void SelectTool::setupObjectInspector()
    {
        m_objectInspectorModel = new ObjectInspectorModel(this);
        m_objectInspectorView = new ObjectInspectorView;
        m_objectInspectorView->setModel(m_objectInspectorModel);
    }

    void SelectTool::setupPropertyBrowser()
    {
        m_propertyEditor = new ItemPropertyEditor();
    }

    void SelectTool::setupArrangeTools()
    {
        static const QSize IconSize(24, 24);
        static const QMargins NullMargin;
        static const int NullSpacing = 0;

        m_arrangeWidget = new QWidget;
        auto layout = new QGridLayout();
        layout->setContentsMargins(NullMargin);
        layout->setSpacing(NullSpacing);
        layout->addWidget(new QLabel("Arrange"), 0, 0);
        m_arrangeWidget->setLayout(layout);

        m_arrangeHToolBar = new QToolBar();
        m_arrangeHToolBar->setContentsMargins(NullMargin);
        m_arrangeHToolBar->setIconSize(IconSize);
        layout->addWidget(m_arrangeHToolBar, 1, 0);
        m_arrangeHToolBar->addAction(createArrangeAction(Qt::AlignLeft,
                                                         "align-horizontal-left",
                                                         "Align left",
                                                         "a,l"));
        m_arrangeHToolBar->addAction(createArrangeAction(Qt::AlignHCenter,
                                                         "align-horizontal-center",
                                                         "Center horizontally",
                                                         "a,h"));
        m_arrangeHToolBar->addAction(createArrangeAction(Qt::AlignRight,
                                                         "align-horizontal-right",
                                                         "Align right",
                                                         "a,r"));

        m_arrangeHToolBar->addSeparator();
        m_arrangeHToolBar->addAction(createDistributeAction(DistributeLeftBorders, 100,
                                                            "distribute-horizontal-left",
                                                            "Distribute left borders",
                                                            "d,l"));
        m_arrangeHToolBar->addAction(createDistributeAction(DistributeHCenters, 100,
                                                            "distribute-horizontal-center",
                                                            "Distribute centers horizontally",
                                                            "d,c"));
        m_arrangeHToolBar->addAction(createDistributeAction(DistributeHGaps, 100,
                                                            "distribute-horizontal-gaps",
                                                            "Distribute gaps horizontally",
                                                            "d,g"));
        m_arrangeHToolBar->addAction(createDistributeAction(DistributeRightBorders, 100,
                                                            "distribute-horizontal-right",
                                                            "Distribute right borders",
                                                            "d,r"));

        m_arrangeHToolBar->addSeparator();
        m_arrangeHToolBar->addAction(createStackOrderAction(StackToTop,
                                                            "object-order-front",
                                                            "Bring to front",
                                                            "o,f"));
        m_arrangeHToolBar->addAction(createStackOrderAction(StackUp,
                                                            "object-order-raise",
                                                            "Raise",
                                                            "o,r"));

        m_arrangeVToolBar = new QToolBar();
        m_arrangeVToolBar->setContentsMargins(NullMargin);
        m_arrangeVToolBar->setIconSize(IconSize);
        layout->addWidget(m_arrangeVToolBar, 2, 0);
        m_arrangeVToolBar->addAction(createArrangeAction(Qt::AlignTop,
                                                         "align-vertical-top",
                                                         "Align top",
                                                         "a,t"));
        m_arrangeVToolBar->addAction(createArrangeAction(Qt::AlignVCenter,
                                                         "align-vertical-center",
                                                         "Center vertically",
                                                         "a,v"));
        m_arrangeVToolBar->addAction(createArrangeAction(Qt::AlignBottom,
                                                         "align-vertical-bottom",
                                                         "Align bottom",
                                                         "a,b"));

        m_arrangeVToolBar->addSeparator();
        m_arrangeVToolBar->addAction(createDistributeAction(DistributeTopBorders, 100,
                                                            "distribute-vertical-top",
                                                            "Distribute top borders",
                                                            "d,t"));
        m_arrangeVToolBar->addAction(createDistributeAction(DistributeVCenters, 100,
                                                            "distribute-vertical-center",
                                                            "Distribute centers vertically",
                                                            "d,e"));
        m_arrangeVToolBar->addAction(createDistributeAction(DistributeVGaps, 100,
                                                            "distribute-vertical-gaps",
                                                            "Distribute gaps vertically",
                                                            "d,a"));
        m_arrangeVToolBar->addAction(createDistributeAction(DistributeBottomBorders, 100,
                                                            "distribute-vertical-bottom",
                                                            "Distribute bottom borders",
                                                            "d,b"));

        m_arrangeVToolBar->addSeparator();
        m_arrangeVToolBar->addAction(createStackOrderAction(StackToBottom,
                                                            "object-order-back",
                                                            "Bring to back",
                                                            "o,b"));
        m_arrangeVToolBar->addAction(createStackOrderAction(StackDown,
                                                            "object-order-lower",
                                                            "Lower",
                                                            "o,l"));
    }

    QAction *SelectTool::createArrangeAction(Qt::Alignment alignment, const QString &iconName,
                                             const QString &text, const QString &shortcut)
    {
        auto action = new QAction(QIcon::fromTheme(iconName), text, this);
        action->setShortcut(shortcut);
        action->setData(QVariant::fromValue<Qt::Alignment>(alignment));
        connect(action, &QAction::triggered, this, &SelectTool::onArrangeActionTriggered);
        return action;
    }

    QAction *SelectTool::createDistributeAction(Distribution distribution, qreal distance,
                                                const QString &iconName, const QString &text, const QString &shortcut)
    {
        auto action = new QAction(QIcon::fromTheme(iconName), text, this);
        action->setShortcut(shortcut);
        connect(action, &QAction::triggered,
                this, [this, distribution, distance]() {
            auto cmd = new DistributeCommand;
            QList<quint64> ids;
            for (auto item: scene()->selectedObjects())
            {
                ids << scene()->documentIdForItem(item);
            }
            cmd->setDistribution(distribution);
            cmd->setDistance(distance);
            cmd->setItems(ids);
            this->appendCommand(cmd);
        });
        return action;
    }

    QAction *SelectTool::createStackOrderAction(StackOrderOperation operation, const QString &iconName, const QString &text, const QString &shortcut)
    {
        auto action = new QAction(QIcon::fromTheme(iconName), text, this);
        action->setShortcut(shortcut);
        connect(action, &QAction::triggered,
                this, [this, operation]() {
            auto items = scene()->selectedObjects();
            if (items.count() < 1)
            {
                return;
            }
            auto item = items.first();

            GraphicsItem *refItem = nullptr;
            switch (operation)
            {
                case StackToTop:
                {
                    refItem = scene()->highestObscuringItem(item);
                    break;
                }
                case StackUp:
                {
                    refItem = scene()->lowestObscuringItem(item);
                    break;
                }
                case StackDown:
                {
                    refItem = scene()->highestObscuredItem(item);
                    break;
                }
                case StackToBottom:
                {
                    refItem = scene()->lowestObscuredItem(item);
                    break;
                }
            }
            if (refItem == nullptr)
            {
                return;
            }
            auto command = new StackOrderCommand;
            command->setStackOperation(operation);
            command->setItem(scene()->documentIdForItem(item));
            command->setReferenceItem(scene()->documentIdForItem(refItem));
            this->appendCommand(command);
        });
        return action;
    }

    void SelectTool::onArrangeActionTriggered()
    {
        auto action = static_cast<QAction*>(sender());
        auto alignment = action->data().value<Qt::Alignment>();

        auto cmd = new AlignCommand;
        QList<quint64> ids;
        for (auto item: scene()->selectedObjects())
        {
            ids << scene()->documentIdForItem(item);
        }
        cmd->setAlignment(alignment);
        cmd->setItems(ids);
        appendCommand(cmd);
    }
}
